home *** CD-ROM | disk | FTP | other *** search
/ Gigarom 1 / Gigarom Macintosh Archives (Quantum Leap)(CDRM1080320)(1993).iso / FILES / DEV / A-B / 006.PopMenus.cpt / PopMenus.p < prev    next >
Text File  |  1988-08-01  |  13KB  |  353 lines

  1. {------------------------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    Pop-up Menu Example Application
  6. #
  7. #    PopMenus
  8. #
  9. #    PopMenus.p    -    Pascal Source
  10. #
  11. #    Copyright © 1988 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    1.0                    8/88
  15. #
  16. #    Components:    PopMenus.p            August 1, 1988
  17. #                PopMenus.r            August 1, 1988
  18. #                PopMenus.make        August 1, 1988
  19. #
  20. #    This program is a simple example of how to use pop-up menus in your
  21. #    application. It implements a pop-up as a userItem in a modal dialog
  22. #    box (this is a helpful example in its own right!).
  23. #
  24. #    See Sample and TESample for the general structure and MultiFinder 
  25. #    techniques that we recommend that you use when building a new application.
  26. #
  27.  
  28. A few tips:
  29.  
  30. The Menu Manager provides just one routine, PopUpMenuSelect, that you
  31. call when the user clicks on the current selection of your pop-up. Your 
  32. application is responsible for drawing the current selection (and the
  33. title that goes with it), and hilighting the title before calling
  34. PopUpMenuSelect, and unhilighting afterwards.
  35.  
  36. The metrics involved with drawing the current selection are a little
  37. tricky; this example does things in the recommended way. Note the
  38. following as you try the example (it helps to have a long font name
  39. to see some of the extreme cases!):
  40.  
  41. - There is one pixel between the title's boundsrect (as defined in the
  42.   DITL, a staticText item) and the popup's (a userItem). Also, the 
  43.   userItem's boundsrect (as defined) is used as the area INSIDE the
  44.   current selection box. This is the easiest way to handle this; it's
  45.   inconsistent in that normally, clicking on the drop shadow would get
  46.   you what you want (in this case, these clicks are ignored; the user
  47.   must click within the box). The only case where this is a problem
  48.   is if you filter the drawing of items in response to an update event
  49.   to those items that lie within the update region (if just the drop
  50.   shadow needed updating, it wouldn't be updated with this 
  51.   implementation).
  52.  
  53. - The current selection is always drawn in the same place & same
  54.   size. To prevent the selection from drawing wider than the box,
  55.   the selection is truncated and ellipses added (if necessary). This
  56.   algorithm may be handy elsewhere, too! This particular example has
  57.   an especially narrow current selection box (to show off the truncation
  58.   feature); you'll probably want to make yours wide enough to accomodate
  59.   most possible values.
  60.   
  61. - The title is highlighted while the pop-up is up (that is, during
  62.   PopUpMenuSelect), and presumably, during the duration of any operation
  63.   generated by the chosen item. This is intended to work just like the
  64.   hilighting of a regular menu title. 
  65.   
  66. [You need to have more than about seven font families in your system
  67. file to see this one:] A "feature" of the Menu Manager is that it must 
  68. call the menu defproc just once to allocate the space beneath the menu. 
  69. Also, the menu must be kept onscreen (scrolling if necessary). Also, 
  70. the human interface dictates that the currently-chosen item should 
  71. appear under the mouse when the pop-up is presented. These three 
  72. requirements conflict; note the case where the last item is the current
  73. selection.
  74.  
  75. The menu manager will be happy to put the current item whereever you
  76. say it should, but may leave white space in the menu if it's necessary
  77. to "pre-scroll" the menu to give the selection you asked for. This
  78. white space appears because the bits beneath the menu are saved and
  79. restored just once (each).
  80.  
  81. The upshot of all this is that this is a Menu Manager gotcha; there is
  82. no workaround, but the problem may be fixed in future Systems.
  83.  
  84. Pop-up menus must be InsertMenu'd just like hierarchical submenus; they
  85. should left in the menu list only while PopUpMenuSelect is being called.
  86. The example handles this correctly.
  87.  
  88. Applications' pop-up menus' IDs, should always be in the range 1 through 
  89. 235 (inclusive); desk accessories' pop-ups should use IDs 236 through
  90. 255. This really applies to all menus; it's most important for pop-up and
  91. submenus, however.
  92.  
  93. This particular example is meant to show the mechanics of creating and
  94. handling events from submenus; in the interest of simplicity, certain nice
  95. features (like checkmarking the current font) have been omitted.
  96.  
  97. Have fun.
  98.  
  99. ------------------------------------------------------------------------------}
  100.  
  101. PROGRAM PopMenus;
  102. {*
  103.  * Pop-up Menu Example
  104.  * Bryan Stearns 05May87 
  105.  *}
  106.  
  107. USES MemTypes, Quickdraw, OSIntf, ToolIntf, PackIntf;
  108.  
  109. {$R-} {no range checking}
  110. {$D+} {Generate debug symbols}
  111.  
  112. CONST
  113.     myDLOGid = 128;            {our dialog template's resource ID}
  114.     popMenuID = 128;        {our menu's ID}
  115.     myALRTid = 129;         {our “need right machine & sys software” alert}
  116.     
  117.     {Items in our dialog box: there’s an OK button, and…}
  118.     iPopUp = OK+1;            {the Pop-up userItem}
  119.     iPopPrompt = iPopUp+1;    {the Prompt staticText}
  120.     iDefOKRing = iPopPrompt+1;    {the OK-button-default-ring userItem}
  121.  
  122.     {ASCII code for Return and Enter}
  123.     crCode = 13;             {(these are keyboard-independent)}
  124.     enterCode = 3;
  125.  
  126.     {constants for positioning the default item within its box}
  127.     leftSlop = 13;            {leave this much space on left of title}
  128.     rightSlop = 5;            {  this much on right}
  129.     botSlop = 5;            {  this much below baseline}
  130.     
  131. VAR    myDialog: DialogPtr;    {our dialog pointer}
  132.     popUpBox: Rect;            {boundsrect of our popUp's title box}
  133.     promptBox: Rect;        {boundsrect of its prompt}
  134.     popMenu: MenuHandle;    {our popUp's menu}
  135.     hitItem: INTEGER;        {result of ModalDialog}
  136.     lastChoice: INTEGER;    {the last-chosen item from the pop-up menu}
  137.     
  138.     theType: INTEGER;        {used as temp in GetDItem/SetDItem}
  139.     theHdl: Handle;            {used as temp in GetDItem/SetDItem}
  140.     theBox: Rect;            {used as temp in GetDItem/SetDItem}
  141.  
  142.  
  143. {*
  144.  * Draw our popUp’s current selection box
  145.  * Note: This is called by the Dialog Manager (for 
  146.  * update events) as well as our own Filterproc.
  147.  *}
  148. PROCEDURE DrawPopUp(theDialog: DialogPtr; theItem: INTEGER);
  149. VAR r: Rect;
  150.     curFont: Str255;
  151.     newWid, newLen, wid: INTEGER;
  152. BEGIN
  153.     GetItem(popMenu,lastChoice,curFont); {get currently-selected item}
  154.     r := popUpbox;
  155.     WITH r DO BEGIN
  156.         InsetRect(r,-1,-1); {make it a little bigger}
  157.         
  158.         {Make sure the title fits. Truncate it and add an ellipses (“…”)}
  159.         {if it doesn’t (by the way, “…” is option-semicolon)}
  160.         wid := (right - left) - (leftSlop + rightSlop); {available string area}
  161.         newWid := StringWidth(curFont); {get current width}
  162.         IF newWid > wid THEN BEGIN {doesn't fit - truncate it}
  163.             newLen := LENGTH(curFont); {current length in characters}
  164.             wid := wid - CharWidth('…'); {subtract width of ellipses}
  165.             
  166.             REPEAT {until it fits (or we run out of characters)}
  167.                 {drop the last character and its width}
  168.                 newWid := newWid - CharWidth(curFont[newLen]);
  169.                 newLen := PRED(newLen);
  170.             UNTIL (newWid <= wid) OR (LENGTH(curFont) = 0);
  171.             
  172.             {add the ellipses character}
  173.             newLen := SUCC(newLen); {one more char}
  174.             curFont[newLen] := '…'; {it’s the ellipses}
  175.             curFont[0] := CHR(newLen); {fix the length}
  176.         END;
  177.  
  178.         {draw the box and its drop shadow}
  179.         FrameRect(r);
  180.         MoveTo(right,top+2); LineTo(right,bottom);
  181.         LineTo(left+2,bottom);
  182.         
  183.         {draw the string}
  184.         MoveTo(left+LeftSlop,bottom-BotSlop);
  185.         DrawString(curFont);
  186.     END;
  187. END; {DrawPopUp}
  188.  
  189.  
  190. {*
  191.  * Draw the ring around the OK button, the way that
  192.  * alert boxes get it. This procedure is only called 
  193.  * by the Dialog Manager for update events.
  194.  *}
  195. PROCEDURE DrawOKDefault(theDialog: DialogPtr; theItem: INTEGER);
  196. VAR savePen: PenState;
  197. BEGIN
  198.     GetPenState(savePen); {save the old pen state}
  199.     
  200.     GetDItem(theDialog, theItem, theType, theHdl, theBox); {get the item’s rect}
  201.     PenSize(3,3); {make the pen fatter}
  202.     FrameRoundRect(theBox,16,16); {draw the ring}
  203.  
  204.     SetPenState(savePen); {restore the pen state}
  205. END; {DrawOKDefault}
  206.  
  207.  
  208. {*
  209.  * Filterproc for our dialog box
  210.  * - supports Enter & Return --> OK.
  211.  * - watches for userItem hits
  212.  *}
  213. FUNCTION myFilter(theDialog: DialogPtr; VAR theEvent: EventRecord; VAR itemHit: INTEGER): BOOLEAN;
  214. VAR    mouseLoc, popLoc: Point;
  215.     newChoice: INTEGER;
  216.     chosen,ignoreLong: LongInt;
  217. BEGIN    
  218.     itemHit := 0; {We return these two values. Initialize them.}
  219.     myFilter := FALSE;
  220.     SetPort(theDialog);
  221.     
  222.     WITH theEvent DO CASE what OF
  223.         keyDown: BEGIN
  224.             IF (theEvent.message MOD 256) IN [crCode, enterCode] THEN BEGIN
  225.                 {user pressed Return or Enter}
  226.                 GetDItem(theDialog, OK, theType, theHdl, theBox); {get the button's rect}
  227.                 HiliteControl(ControlHandle(theHdl), 1); {make it look...}
  228.                 Delay(3, ignoreLong); {...like the OK button was hit}
  229.                 
  230.                 myFilter := TRUE; {dialog is over}
  231.                 itemHit := OK; {have ModalDialog return that the user hit OK}
  232.             END;
  233.         END; {keydown case}
  234.         
  235.         mouseDown: BEGIN {"Click!"}
  236.             mouseLoc := where; {copy the mouse position}
  237.             GlobalToLocal(mouseLoc); {convert it to local coordinates}
  238.             
  239.             {Was the click in our item?}
  240.             IF (FindDItem(theDialog, mouseLoc) + 1) = iPopUp THEN BEGIN {Yep, the click was ours!}
  241.             
  242.                 {We're going to pop up our menu. Insert our menu into the menu list,}
  243.                 {then call CalcMenuSize (to work around a bug in the Menu Manager), }
  244.                 {then call PopUpMenuSelect and let the user drag around. Note that the}
  245.                 {(top,left) parameters to PopUpMenuSelect are our item’s, converted to}
  246.                 {global coordinates.}
  247.                 InvertRect(promptBox); {hilight the prompt}
  248.                 InsertMenu(popMenu,-1); {insert our menu in the menu list}
  249.                 popLoc := popUpBox.topLeft; {copy our item’s topleft}
  250.                 LocalToGlobal(popLoc); {convert back to global coords}
  251.                 CalcMenuSize(popMenu); {Work around Menu Mgr bug}
  252.                 WITH popLoc DO chosen := PopUpMenuSelect(popMenu, v, h, lastChoice);
  253.                 InvertRect(promptBox); {unhilight the prompt}
  254.                 DeleteMenu(popMenuID); {remove our menu from the menu list}
  255.                 
  256.                 {Was something chosen?}
  257.                 IF chosen <> 0 THEN BEGIN {yep, something was chosen}
  258.                     newChoice := LoWord(chosen); {get the chosen item number}
  259.                     
  260.                     IF newChoice <> lastChoice THEN BEGIN
  261.                         {the user chose an item other than the current one}
  262.                         SetItemMark(popMenu,lastChoice,' '); {unmark the old choice}
  263.                         SetItemMark(popMenu,newChoice,CHR(checkMark)); {mark the new choice}
  264.                         lastChoice := newChoice; {update the current choice}
  265.                         
  266.                         {Draw the new title}
  267.                         EraseRect(popUpBox);
  268.                         DrawPopUp(theDialog,iPopUp);
  269.                         
  270.                         myFilter := TRUE; {dialog is over}
  271.                         itemHit := iPopUp; {have ModalDialog return that the user changed items}
  272.                     END; {if this choice was not the current choice}
  273.                 END; {if something was chosen}
  274.  
  275.             END; {if clicked in our userItem}
  276.         END; {mousedown case}
  277.     END {case}
  278. END; {myFilter}
  279.  
  280.  
  281. {*
  282.  * Main
  283.  *}
  284. BEGIN
  285.     {Initialize all the usual managers}
  286.     InitGraf(@thePort);    
  287.     InitFonts;            
  288.     FlushEvents(everyEvent,0);
  289.     InitWindows;                
  290.     InitMenus;            
  291.     TEInit;
  292.     InitDialogs(NIL);            
  293.     InitCursor;            
  294.     
  295.     {Check to make sure we’re running on a machine}
  296.     {capable of supporting pop-up menus}
  297.     (** SysEnvirons(xxx); **)
  298.     IF FALSE THEN BEGIN {Sorry, see your dealer}
  299.         hitItem := StopAlert(myALRTid,NIL); {put up the alert}
  300.         ExitToShell; {back to the finder}
  301.     END;
  302.     
  303.     {Get a menu containing the current set of fonts}
  304.     popMenu := NewMenu(popMenuID,'notUsed'); {Create a menu (its title is ignored)}
  305.     AddResMenu(popMenu,'FONT'); {fill it with fonts}
  306.     lastChoice := 1; {make the first item the default}
  307.     SetItemMark(popMenu,1,CHR(checkMark)); {check it}
  308.     
  309.     {Get our dialog box, and set up our useritem (the dialog template}
  310.     {defines this dialog to be hidden, so that it won’t be drawn until}
  311.     {we’ve installed our userItem’s drawing procedure)}
  312.     myDialog := GetNewDialog(myDLOGid,NIL,POINTER(-1));
  313.         
  314.     {Find out where our popUp's rectangle is, and set its item handle to be}
  315.     {a pointer to our popup-drawing procedure}
  316.     GetDItem(myDialog,iPopUp,theType,theHdl,popUpbox); {get the rect}
  317.     SetDItem(myDialog,iPopUp,theType,@DrawPopUp,popUpbox); {set the procPtr}
  318.     
  319.     {Find out where the prompt for our popUp is, so that we can invert its}
  320.     {rect when popping up our menu}
  321.     GetDItem(myDialog,iPopPrompt,theType,theHdl,promptBox); {get the rect}
  322.     
  323.     {Move our default-OK-button userItem to around the OK button, and set its}
  324.     {item handle to be a pointer to our other drawing procedure}
  325.     GetDItem(myDialog,OK,theType,theHdl,theBox); {get the OK button's rect}
  326.     InsetRect(theBox,-4,-4); {make the rect a little bigger}
  327.     {set the same old type, our procptr, and the new box}
  328.     SetDItem(myDialog,iDefOKRing,userItem+itemDisable,@DrawOKDefault,theBox);
  329.  
  330.     ShowWindow(myDialog); {show it, finally!}
  331.     
  332.     REPEAT
  333.     
  334.         ModalDialog(@myFilter,hitItem);
  335.         
  336.         CASE hitItem OF        {which item was hit, if any?}
  337.         
  338.             iPopUp: BEGIN        {he chose a new item in our pop-up}
  339.                 {Do whatever needs to be done when the choice changes; }
  340.                 {the on-screen current value box has already been updated.}
  341.             END; {iPopUp case}
  342.             
  343.             {other items here}
  344.             
  345.         END; {case}
  346.         
  347.     UNTIL hitItem = OK;
  348.     
  349.     HideWindow(myDialog); {hide the window, as feedback}
  350. END.
  351.  
  352.  
  353.